Axis = Class.create({
 initialize: function (origoX, origoY, deltaX, deltaY, scaleX, scaleY) {
   this._origoX = origoX;
   this._origoY = origoY;
   this._deltaX = deltaX;
   this._deltaY = deltaY;
   this._scaleX = scaleX;
   this._scaleY = -scaleY;
 },
 transX: function(value, element) {
   var dx = 0;
   if (element && element.getLUPX)
     dx = element.getLUPX;
   return this._origoX + (value - this._deltaX) * this._scaleX - dx;
 },
 transY: function(value, element) {
   var dy = 0;
   if (element && element.getLUPY)
     dy = element.getLUPY;
   return this._origoY + (value - this._deltaY) * this._scaleY - dy;
 }, 
 scaleX: function(value) {
   return value * this._scaleX;
 }, 
 scaleY: function(value) {
   return value * (-this._scaleY);
 }
});

BubbleAnim = Class.create(S2.FX.Base, {
  initialize: function($super, options) {
    $super(options);
    this._originalR = parseFloat(this.options.element.getAttributeNS(null, 'r'));
  },
  update: function (p) {
    this.options.element.setAttributeNS(null, 'r', this._originalR + ((this.options.r - this._originalR) * p));
  }
});

FaceAnim = Class.create(S2.FX.Base, {
  initialize: function($super, options) {
    $super(options);
//    this._originalX = parseFloat(this.options.element.getAttributeNS(null, 'x'));
//    this._originalY = parseFloat(this.options.element.getAttributeNS(null, 'y'));
    this._originalX = 0;
    this._originalY = 0;
    var transform = this.options.element.getAttributeNS(null, 'transform');
    var values = transform.substring(10, transform.length - 1);
    if (values) {
      var splitted = values.split(',');
      this._originalX = parseFloat(splitted[0])||0;
      this._originalY = parseFloat(splitted[1])||0;
    }
  },
  update: function (p) {
    var currentX = this._originalX + ((this.options.x - this._originalX) * p);
    var currentY = this._originalY + ((this.options.y - this._originalY) * p);
    this.options.element.setAttributeNS(null, 'transform', 'translate(' + currentX + ',' + currentY + ')');
    
    // this.options.element.setAttributeNS(null, 'x', currentX);
    // this.options.element.setAttributeNS(null, 'y', currentY);
  }
});

FaceAppear = Class.create(S2.FX.Base, {
  initialize: function($super, options) {
    $super(options);
    this._original = this.options.element.style.opacity;
    this._skip = this.options.element.style.opacity == this.options.opacity;
  },
  update: function (p) {
    if (!this._skip)
      this.options.element.style.opacity = this._original + ((this.options.opacity - this._original) * p);
  }
});

IndexController = Class.create({
  initialize : function() {
    this._chartContainer = $('chart');
    
    this._bubbles = null;
    this._faces = new Object();
    this._pollInternal = 5 * 1000;
    this._animationDuration = 2;
    this._random = 0;
    
    this._maxValue = 3;
    
    this._pageWidth = 510;
    this._pageHeight = 510;
    this._faceWidth = 100;
    this._faceHeight = 100;
    this._roundStroke = 8;
    
    this._bubblesX = 0;
    this._bubblesY = 0;
    this._page = -1;
    this._polling = false;
    this._bubbleMul = 5;
    this._unanonymousUsers = new Array();
    
    // tuotanto
    this._unanonymousUsers.push(1129);
    this._unanonymousUsers.push(1299);
    this._unanonymousUsers.push(1300);
    this._unanonymousUsers.push(1127);
    
    this._unanonymousUsersNames = new Array();
    
    this._unanonymousUsersNames.push("Jan-Erik Laine");
    this._unanonymousUsersNames.push("Marjo Kaipainen");
    this._unanonymousUsersNames.push("Jarmo Eskelinen");
    this._unanonymousUsersNames.push("Soile Kuitunen");
    
    this._createDefs();
    this._createChart();
    
    var _this = this;
    this._readMeta(function () {
      _this._switchPage('INDEX');
      _this._poll();
    });
  },
  
  _createDefs: function () {
    this._chartDocument = new fi.foyt.svg.svgdom.FNISVGDocument();
    this._chartDocument.setPageSize(this._pageWidth, this._pageHeight);
    this._chartDocument.setViewBox(0, 0, this._pageWidth, this._pageHeight);
    this._chartContainer.appendChild(this._chartDocument.getRootElement());
    
    var defs = this._chartDocument.createElement("defs");
    
    var bubbleGradient = this._chartDocument.createElement("radialGradient");
    bubbleGradient.setAttributeNS(null, "id", "bubbleGradient");

    var bubbleGradientStop1 = this._chartDocument.createElement("stop");
    bubbleGradientStop1.setAttributeNS(null, "style", "stop-color:#ee3333;stop-opacity:0.5");
    bubbleGradientStop1.setAttributeNS(null, "offset", "0");
    this._chartDocument.appendElement(bubbleGradient, bubbleGradientStop1);

    var bubbleGradientStop2 = this._chartDocument.createElement("stop");
    bubbleGradientStop2.setAttributeNS(null, "style", "stop-color:#711100;stop-opacity:1;");
    bubbleGradientStop2.setAttributeNS(null, "offset", "1");
    this._chartDocument.appendElement(bubbleGradient, bubbleGradientStop2);
    this._chartDocument.appendElement(defs, bubbleGradient);
    
    var clipPath = this._chartDocument.createElement("clipPath");
    clipPath.setAttributeNS(null, "id", "imageclip");
    this._chartDocument.appendElement(defs, clipPath);

    var imageclip = this._chartDocument.createElement("rect");
    imageclip.setAttributeNS(null, 'x', this._roundStroke * 1.25);
    imageclip.setAttributeNS(null, 'y', this._roundStroke * 1.25);
  	imageclip.setAttributeNS(null, 'rx', this._roundStroke);
  	imageclip.setAttributeNS(null, 'ry', this._roundStroke);
    imageclip.setAttributeNS(null, 'width', this._faceWidth - this._roundStroke * 2.5);
    imageclip.setAttributeNS(null, 'height', this._faceHeight - this._roundStroke * 2.5);
    imageclip.setAttributeNS(null, 'style', "fill:black;stroke-width:0;");
    
    this._chartDocument.appendElement(clipPath, imageclip);
    
    this._bubblesLayer = this._chartDocument.createLayer('bubbles');
    this._facesLayer = this._chartDocument.createLayer('faces');

    this._chartDocument.appendElement(this._chartDocument.getRootElement(), defs);
  },
  
  _createChart: function () {
    var arrowSize = 20;
    var arrowMargin = 10;
    var arrowStyle = 'stroke: rgb(255, 255, 255); fill: rgb(255, 255, 255);';
    var lineSize = 5;
    var lineStyle = 'stroke: rgb(255, 255, 255);';
    
    this._upArrow = this._drawArrow(arrowSize, arrowStyle, 'UP');
    this._chartDocument.appendElement(this._chartDocument.getBackgroundGroup(), this._upArrow);
    this._moveElementTo(this._upArrow,  (this._pageWidth / 2) - (arrowSize / 2), arrowMargin);

    this._downArrow = this._drawArrow(arrowSize, arrowStyle, 'DOWN');
    this._chartDocument.appendElement(this._chartDocument.getBackgroundGroup(), this._downArrow);
    this._moveElementTo(this._downArrow, (this._pageWidth / 2) - (arrowSize / 2), this._pageHeight - (arrowMargin + arrowSize));

    this._leftArrow = this._drawArrow(arrowSize, arrowStyle, 'LEFT');
    this._chartDocument.appendElement(this._chartDocument.getBackgroundGroup(), this._leftArrow);
    this._moveElementTo(this._leftArrow, arrowMargin, (this._pageHeight / 2) - (arrowSize / 2));

    this._rightArrow = this._drawArrow(arrowSize, arrowStyle, 'RIGHT');
    this._chartDocument.appendElement(this._chartDocument.getBackgroundGroup(), this._rightArrow);
    this._moveElementTo(this._rightArrow, this._pageWidth - (arrowMargin + arrowSize), (this._pageHeight / 2) - (arrowSize / 2));
    
    this._axisLineX = this._drawLine(lineSize, lineStyle, arrowMargin + arrowSize, (this._pageHeight / 2), this._pageWidth - (arrowMargin + arrowSize), (this._pageHeight / 2));
    this._chartDocument.appendElement(this._chartDocument.getBackgroundGroup(), this._axisLineX);
    
    this._axisLineY = this._drawLine(lineSize, lineStyle, this._pageWidth / 2, arrowMargin + arrowSize, this._pageWidth / 2, this._pageHeight - (arrowMargin + arrowSize));
    this._chartDocument.appendElement(this._chartDocument.getBackgroundGroup(), this._axisLineY);
  },
  
  _createFaces: function () {
    for (var i = 0, l = this._unanonymousUsers.length; i < l; i++) {
      var userId = this._unanonymousUsers[i];
      var userName = this._unanonymousUsersNames[i];
      
      var faceImage = this._createFace(this._faceWidth, this._faceHeight, CONTEXTPATH + "/_app/?action=FACE&userId=" + userId, userName);
      
      var x = Math.round(this._axis.transX((this._bubblesX - 1) / 2, faceImage));
      var y = Math.round(this._axis.transY((this._bubblesY - 1) / 2, faceImage));
      faceImage.setAttributeNS(null, 'style', 'opacity: 0');
      faceImage.setAttributeNS(null, 'transform', 'translate(' + x + ',' + y + ')');
      
      this._faces[userId] = faceImage;
      this._chartDocument.appendElement(this._facesLayer, faceImage);
    }
  },
  _getFace: function (userId) {
    return this._faces[userId];
  },
  
  /**
   * MoveFace
   * 
   * @param element
   * @param x
   * @param y
   */
  _moveFace: function (element, x, y) {
    
    if (element._anim) {
      element._anim.finish();
      delete element._anim;
      element._anim = undefined;
    }
    
    if (element._appear) {
      element._appear.finish();
      delete element._appear;
      element._appear = undefined;
    }
    
    if (!element.organized) {
    	
    	element.organize();
    	element.organized = true;
    }
    
    element._anim = new FaceAnim({
      element: element, 
      x: Math.round(this._axis.transX(x, element)),
      y: Math.round(this._axis.transY(y, element)),
      duration: this._animationDuration,
      after: function() {
        element._anim = undefined;
      }
    }).play().start();
    
    element._appear = new FaceAppear({
      element: element,
      opacity: 1,
      duration: 2,
      after: function() {
        element._appear = undefined;
      }
    }).play().start();
  },
  
  _readMeta: function (callback) {
    var _this = this;
    
    new Ajax.Request(CONTEXTPATH + "/_app?action=QUERY_META", {
      onSuccess: function (transport) {
        _this._queryMeta = transport.responseText.evalJSON();
        callback();
      }
    });
  },
  
  _removeBubbles: function () {
    for (var x = 0; x < this._bubblesX; x++) {
      for (var y = 0; y < this._bubblesY; y++) {
        var i = x + (y * this._bubblesX);
        this._chartDocument.removeElement(this._bubbles[i]);
      }
    }
  },
  
  _removeFaces: function () {
    for (var i = 0, l = this._unanonymousUsers.length; i < l; i++) {
      var userId = this._unanonymousUsers[i];
      var face = this._faces[userId];
      if (face)
        this._chartDocument.removeElement(face); 
    }
  },
  
  _switchPage: function (page) {
    this._stopPolling();
     
    this._page = page;
    var _this = this;

    if (page == 'INDEX') {
      $('index').appear();
      new S2.FX.Morph($('graph'), {
        style: "opacity: 0",
        engine: 'javascript',
        after: function () {
          _this._removeBubbles();
          _this._removeFaces();
        }
      }).play();
      this._startPolling();
    } else {
      $('index').fade();
      
      // Fade old graph
      new S2.FX.Morph($('graph'), {
        style: "opacity: 0",
        engine: 'javascript',
        duration: 2,
        after: function () {
          var pageMeta = null;
          for (var pageIndex = 0; pageIndex < _this._queryMeta.pages.length; pageIndex++) {
            if (_this._queryMeta.pages[pageIndex].number == _this._page) {
              pageMeta = _this._queryMeta.pages[pageIndex];
              break;
            }
          }
          
          // Remove all old stuff
          _this._removeBubbles();
          _this._removeFaces();
          // Do all the initialization magic
          _this._bubbles = new Object(); 
          _this._bubblesX = parseInt(pageMeta.xValueCount);
          _this._bubblesY = parseInt(pageMeta.yValueCount);
          _this._axis = new Axis(_this._pageWidth / 2.0, _this._pageHeight / 2.0, _this._maxValue, _this._maxValue, _this._pageWidth / _this._bubblesX, _this._pageHeight / _this._bubblesY);
          _this._createBubbleMatrix();
          _this._createFaces();
          $('thesis').update(pageMeta.thesis);
          // Start polling again 
          _this._startPolling();
          // and show the new graph
          new S2.FX.Morph($('graph').setStyle({
            opacity: 0,
            display: ''
          }), {
            style: "opacity: 1",
            engine: 'javascript',
            duration: 2
          }).play();
        }
      }).play();
    }
  },
  
  _createBubbleMatrix: function() {
  	for (var x = 0; x < this._bubblesX; x++) {
      for (var y = 0; y < this._bubblesY; y++) {
        var i = x + (y * this._bubblesX);
        var bubble = this._createBubble(x, y);
        this._bubbles[i] = bubble;
        this._chartDocument.appendElement(this._bubblesLayer, bubble);
      }
    }
  },
  
  _createCoords: function() {
  	for (var x = 0; x < this._bubblesX; x++) {
      for (var y = 0; y < this._bubblesY; y++) {
        
        var text = this._chartDocument.createElement("text");
        text.textContent = "(" + x +"," + y +")";
        
        text.setAttributeNS(null, 'x', this._axis.transX(x));
        text.setAttributeNS(null, 'y', this._axis.transY(y));
        
        this._chartDocument.appendElement(this._chartDocument.getRootElement(), text);
      }
    }
  },
  
  
  _drawArrow: function (size, style, dir) {
    var element = this._chartDocument.createElement("path");
    var controller = getSVGElementControllerVault().getElementControllerFor(element);
    var segment = null;
    
    switch (dir) {
      case 'UP':
        segment = new fi.foyt.svg.svgdom.FNISVGPathElementSegment('M', true, [{
          x: 0,
          y: size
        }, {
          x: size / 2,
          y: 0
        }, {
          x: size,
          y: size
        }]);
      break;
      case 'DOWN':
        segment = new fi.foyt.svg.svgdom.FNISVGPathElementSegment('M', true, [{
          x: 0,
          y: 0
        }, {
          x: size / 2,
          y: size
        }, {
          x: size,
          y: 0
        }]);
      break;
      case 'LEFT':
        segment = new fi.foyt.svg.svgdom.FNISVGPathElementSegment('M', true, [{
          x: size,
          y: 0
        }, {
          x: 0,
          y: size / 2
        }, {
          x: size,
          y: size
        }]);
      break;
      case 'RIGHT':
        segment = new fi.foyt.svg.svgdom.FNISVGPathElementSegment('M', true, [{
          x: 0,
          y: 0
        }, {
          x: size,
          y: size / 2
        }, {
          x: 0,
          y: size
        }]);
      break;
    }
    
    controller.appendSegment(element, segment);
    element.setAttributeNS(null, 'style' ,style);
    controller.appendSegment(element, new fi.foyt.svg.svgdom.FNISVGPathElementSegment('Z', true, []));
    controller.updatePath(element);
    
    return element;
  },
  _drawLine: function (size, style, x1, y1, x2, y2) {
    var element = this._chartDocument.createElement("line");
    element.setAttributeNS(null, 'x1', x1);
    element.setAttributeNS(null, 'y1', y1);
    element.setAttributeNS(null, 'x2', x2);
    element.setAttributeNS(null, 'y2', y2);
    
    element.setAttributeNS(null, 'style' , style + ';stroke-width:' + size);
    
    return element;
  },
  
  _createBubble: function (x, y) {
  	
  	var element = this._chartDocument.createElement("circle");
    element.getLUPX = 0;
    element.getLUPY = 0;

    element.setAttributeNS(null, 'cx', this._axis.transX(x, element));
    element.setAttributeNS(null, 'cy', this._axis.transY(y, element));
    element.setAttributeNS(null, 'r', "0");
    element.setAttributeNS(null, 'style', 'fill:url(#bubbleGradient); opacity: 0.75');
    
    return element;
  },
  
  
  _createFace: function (width, height, url, name) {
  	
  	var group = this._chartDocument.createElement("g");
  	
  	group.getLUPX = width / 2.0;
  	group.getLUPY = height / 2.0;
  	
  	var rect = this._chartDocument.createElement("rect");
  	rect.setAttributeNS(null, 'x', this._roundStroke);
  	rect.setAttributeNS(null, 'y', this._roundStroke);
    rect.setAttributeNS(null, 'rx', this._roundStroke);
    rect.setAttributeNS(null, 'ry', this._roundStroke);
    rect.setAttributeNS(null, 'width', width - this._roundStroke * 2);
    rect.setAttributeNS(null, 'height', height - this._roundStroke * 2);
    rect.setAttributeNS(null, 'style', "fill:white;stroke:black;stroke-width:" + this._roundStroke + ";opacity:0.5");
    
    this._chartDocument.appendElement(group, rect);

    var element = this._chartDocument.createElement("image");
    element.setAttributeNS(null, 'x', this._roundStroke);
    element.setAttributeNS(null, 'y', this._roundStroke);
    element.setAttributeNS(null, 'width', width - 2 * this._roundStroke);
    element.setAttributeNS(null, 'height', height - 2 * this._roundStroke);
    element.setAttributeNS(null, 'style', "opacity: 0.85");
    element.setAttributeNS(null, 'clip-path', "url(#imageclip)");
    element.setAttributeNS("http://www.w3.org/1999/xlink", 'xlink:href', url);
    
    this._chartDocument.appendElement(group, element);
        
    var text = this._chartDocument.createElement("text");
    text.textContent = name;
    
    this._chartDocument.appendElement(group, text);
    
    var bbox = text.getBBox();
    var textW = text.getComputedTextLength();
    
    text.setAttributeNS(null, 'x', (width - textW) / 2);
    text.setAttributeNS(null, 'y', height + 20 + bbox.height);
    text.setAttributeNS(null, 'style', 'font-style: bold;');
    
    group.setAttributeNS(null, 'width', width);
    
    group.setAttributeNS(null, 'height', height + text.getBBox().height + 20 );
    
    group.organize = function() {
    	var text = this.getElementsByTagName('text')[0];

    	var bb = text.getBBox();
    	
    	text.setAttributeNS(null, 'x', (width - bb.width) / 2);
    	text.setAttributeNS(null, 'y', height + bb.height - 4);
    	text.parentNode.height = height + bb.height + 5;
    };
    
    return group;
  },
  
  _animateBubble: function (element, r) {
    if (element._anim) {
      element._anim.finish();
      delete element._anim;
      element._anim = undefined;
    }
    
    element._anim = new BubbleAnim({
      element: element, 
      r: r * this._bubbleMul,
      duration: this._animationDuration,
      after: function(){
        element._anim = undefined;
      }
    }).play().start();
  },
  _getBubble: function (x, y) {
    return this._bubbles[x + (y * this._bubblesY)];
  },
  _moveElementTo: function (element, x, y) {
    var controller = getSVGElementControllerVault().getElementControllerFor(element);
    controller.moveTo(element, x, y);
  },
  _startPolling: function () {
    this._polling = true;
  },
  _stopPolling: function () {
    this._polling = false;
  },
  
  _poll: function () {
    var _this = this;
    var url = CONTEXTPATH + "/_app?action=UPDATE&page=" + this._page;
    for (var i = 0, l = this._unanonymousUsers.length; i < l; i++) {
      url += "&unanonymousUsers=" + this._unanonymousUsers[i];
    }
      
    if (this._random) {
      url += "&random=" + this._random;  
    }
    
    new Ajax.Request(url, {
      onComplete: function () {
        setTimeout(function() {
          _this._poll();
        }, _this._pollInternal);
      },
      
      onSuccess: function (transport) {
        if (_this._polling == true) {
          var stop = false;
          var jsonResponse = transport.responseText.evalJSON();
          if (jsonResponse.commands) {
            for (var i = 0, l = jsonResponse.commands.length; i < l; i++) {
              var command = jsonResponse.commands[i];
              switch (command.action) {
                case 'PAGE':
                  stop = false;
                  _this._switchPage(command.args);
                break;
              }
            }
          }
          
          if (!stop) {
            for (var i = 0, l = jsonResponse.values.length; i < l; i++) {
              var value = jsonResponse.values[i];
              var bubble = _this._getBubble(value.x, value.y);
              _this._animateBubble(bubble, value.value, _this._animationDuration);
            }
            
            for (var i = 0, l = jsonResponse.unanonymousValues.length; i < l; i++) {
              var unanonymousValue = jsonResponse.unanonymousValues[i];
              var faceImage = _this._getFace(unanonymousValue.userId);
              if (unanonymousValue.x != null && unanonymousValue.y != null) {
              	_this._moveFace(faceImage, unanonymousValue.x, unanonymousValue.y);
              }
            }
          }
        }
      }
    });
  }
});

document.observe("dom:loaded", function(event) {
  window._indexController = new IndexController();
});